﻿#region License

// PentaBiz - Sustainable Software Development Framework Copyright (C) 2013 Zoltán Csizmazia
// 
// This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

#endregion

#define SECURED_SERVICE
using System;
using System.Data.Services;
using System.Data.Services.Common;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using PentaBiz.Cloud.CloudDb;
using PentaBiz.Cloud.CloudDb.EntityModel.SystemModel;
using PentaBiz.Cloud.Services.QueryService.OData;
using PentaBiz.Diagnostics;

namespace PentaBiz.Cloud.Services.QueryService
{
    /// <summary>
    ///     Defines the CloudDataService{T}.
    /// </summary>
    /// <typeparam name="TQueryContext">The type of the query context.</typeparam>
    /// <typeparam name="TDbContext">The type of the db context.</typeparam>
    [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
    [ServiceMonitorBehavior]
    public class CloudQueryService<TQueryContext, TDbContext> :
        ODataService<TQueryContext>, ICloudQueryService
        where TQueryContext : CloudQueryContext<TDbContext>, new()
        where TDbContext : ScopedDbContext<TDbContext>, new()
    {
        // This method is called only once to initialize service-wide policies.
        public static void InitializeService(DataServiceConfiguration config)
        {
            //grant all rights
            config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);

            config.SetServiceOperationAccessRule("*", ServiceOperationRights.ReadSingle);


            //set default page size to 100
            config.SetEntitySetPageSize("*", 100);

            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;

#if DEBUG
            // Set to 'true' during debugging.
            config.UseVerboseErrors = true;
#endif
        }

        /// <summary>
        ///     Creates the data source.
        /// </summary>
        /// <returns></returns>
        protected override TQueryContext CreateDataSource()
        {
            //create context
            TQueryContext context = base.CreateDataSource();

            //lock the context
            context.LockContext();

            return context;
        }

        /// <summary>
        ///     Called when [start processing request].
        /// </summary>
        /// <param name="args">The args.</param>
        /// <exception cref="System.Data.Services.DataServiceException">403;Access Denied!</exception>
        protected override void OnStartProcessingRequest(ProcessRequestArgs args)
        {
            //TODO: add performance counters here.
            //http://perfmoncounterhelper.codeplex.com/
            //NumberOfRequests
            //RequestsPerSec
            //ProcessTime
            //NumberOfUnathorizedRequests
            //UnathorizedRequstsPerSecond


            //get request headers
            WebOperationContext webcontext = WebOperationContext.Current;

            //ensure webcontext is not null
            EnsureWebOperationContext(webcontext);


            //authorize the request
            AuthorizeRequest(webcontext, args.RequestUri);

            base.OnStartProcessingRequest(args);
        }


        /// <summary>
        ///     Ensures the web operation context.
        /// </summary>
        /// <param name="webOperationContext">The webcontext.</param>
        /// <exception cref="System.Data.Services.DataServiceException">Thrown when WebOperationContext is null!</exception>
        private static void EnsureWebOperationContext(WebOperationContext webOperationContext)
        {
            if (webOperationContext == null)
            {
                Trace.TraceWarning("Access Denied, WebOperationContext is null");
                throw new DataServiceException(403, "Access Denied. WebOperationContext is null!");
            }
        }

        /// <summary>
        ///     Authorizes the request.
        /// </summary>
        /// <param name="webOperationContext">The web operation context.</param>
        /// <param name="requestUri">The request URI.</param>
        /// <exception cref="System.ArgumentNullException">
        ///     Thrown if <paramref name="webOperationContext" /> is null
        /// </exception>
        /// <exception cref="System.Data.Services.DataServiceException">Thrown if authorization fails and there are no tokens with the given httpheader parameters</exception>
        /// <exception cref="System.ArgumentNullException">
        ///     Thrown if <paramref name="webOperationContext" /> is null
        /// </exception>
        [Conditional("SECURED_SERVICE")]
        private void AuthorizeRequest(WebOperationContext webOperationContext, Uri requestUri)
        {
            if (webOperationContext == null) throw new ArgumentNullException("webOperationContext");
            if (requestUri == null) throw new ArgumentNullException("requestUri");

            string[] segments = requestUri.Segments;
            string lastSegment = segments.Last();

            if (lastSegment != "login" && !lastSegment.EndsWith(".svc/"))
            {
                WebHeaderCollection headers = webOperationContext.IncomingRequest.Headers;

                string scope = headers.Get("ScopeName");
                string username = headers.Get("UserName");
                string token = headers.Get("Token");


                //authorize the current request
                SystemToken securityToken = CurrentDataSource.AuthorizeUser(scope, username, token);

                //check if authorization passed
                if (securityToken == null)
                {
                    Trace.TraceWarning("Access Denied! Not Authenticated request.");


                    throw new DataServiceException(403,
                                                   "Access Denied!\r\nTo authorize you must login with /Login?ScopeName='ScopeName'&UserName='UserName'&Password='Password'\r\nAfter login you must provide these Header values with the request ScopeName, UserName, Token");
                }
            }
        }


        /// <summary>
        ///     Logins the user.
        /// </summary>
        /// <param name="scopeName">Name of the scope.</param>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <returns></returns>
        [WebGet]
        [SingleResult]
        public string LoginUser(string scopeName, string username, string password)
        {
            SystemToken token = CurrentDataSource.LoginUser(scopeName, username, password);

            return token == null ? "" : token.Token;
        }
    }
}